﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Compilex.Syntatic.Descriptors;

namespace Compilex.Syntatic.Declarations
{
    public class FunctionCall
    {
        public SyntaticContext Context { get; set; }
        public string FunctionName { get; set; }
        public int ParametersCalledCount { get; set; }
        public FunctionDescriptor FunctionDescriptor
        {
            get
            {
                return (FunctionDescriptor)Context.ScopeManager.CurrentScope.
                GetDeclarationAnywhere(SymbolClass.Funcex, FunctionName).Descriptor;
            }
        }

        public SymbolType NextParameterType
        {
            get
            {
                return FunctionDescriptor.Params[ParametersCalledCount].Type;
            }
        }

        public int NextParameterOffset
        {
            get
            {
                return FunctionDescriptor.Params[ParametersCalledCount].StackOffset;
            }
        }

        public SymbolType ReturnType
        {
            get
            {
                return FunctionDescriptor.ReturnType;
            }
        }

        public int ParametersCount
        {
            get
            {
                return FunctionDescriptor.Params.Count;
            }
        }

        public FunctionCall(SyntaticContext context)
        {
            this.Context = context;
        }

        public void ReadBExpressionParameter()
        {
            Context.ExpressionStack.EndExpression();
            // check if this parameter is expected
            if (Context.ExpressionStack.JustPopped.Type != NextParameterType)
                throw new SyntaticException("Parametro " + ParametersCalledCount
                    + " da chamada da função " + FunctionName + " não corresponde ao declarado.");

            ParametersCalledCount++;

            GenerateSimpleArgumentPush();
        }

        public void ReadLiteralParameter()
        {
            // check if this parameter is expected
            if (NextParameterType != new SymbolType(TypeValue.cadeiex))
                throw new SyntaticException("Parametro " + ParametersCalledCount
                    + " da chamada da função " + FunctionName + " não corresponde ao declarado.");

            ParametersCalledCount++;

            GenerateLiteralArgumentPush();
        }

        protected virtual void GenerateSimpleArgumentPush()
        {
            this.Context.CommandBlockStack.Current.CodeGenerator.PushSimpleArgument();
        }

        protected virtual void GenerateLiteralArgumentPush()
        {
            string literal = this.Context.TokenProvider.PeekNextTokenLexeme();
            this.Context.CommandBlockStack.Current.CodeGenerator.PushLiteralArgument(literal);
        }

        public void EndFunctionCall()
        {
            if (ParametersCalledCount != ParametersCount)
            {
                throw new SyntaticException("Número de argumentos na chamada para função " + FunctionName + " não corresponde à sua declaração.");
            }
            GenerateCode();
        }

        public virtual void GenerateCode()
        {
            Context.CommandBlockStack.Current.CodeGenerator.EndFunctionCall(FunctionDescriptor, FunctionName);
        }
    }
}
